package com.michael.demos.base.reflect.annocation.ioc;

import com.michael.demos.base.reflect.annocation.ioc.annocation.Autowired;
import com.michael.demos.base.reflect.annocation.ioc.annocation.Component;
import com.michael.demos.base.reflect.annocation.ioc.tool.Assert;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 类功能描述:
 * <pre>
 *   反射 + 注解 - 容器类
 * </pre>
 *
 * @author Michael
 * @version 1.0
 * @date 2020/8/5 16:56
 */
public class ApplicationContext {

    /** 存储 bean 名称与对象 */
    private Map<String, Object> beanMap = new ConcurrentHashMap(32);

    /** 需要处理的类集体 */
    private Set<String> classPathSet = new HashSet<>();

    public ApplicationContext() throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        /** 扫描路径 */
        String scanPath = "com.michael.demos.base.reflect.annocation.ioc.client";
        scanPath = scanPath.replace(".", "/");
        scanClassFile(scanPath);

        // 处理@Component
        for (String classPath : classPathSet) {
            doComponent(Class.forName(classPath));
        }

        // 处理@Autowired
        for (String beanName : beanMap.keySet()) {
            doIoc(beanMap.get(beanName));
        }
    }

    /** 单词首字母小写 */
    private String getBeanMapKey(String name) {
        if (StringUtils.isNotEmpty(name)) {
            return name.substring(0, 1).toLowerCase() + name.substring(1);
        }
        return name;
    }

    private void doIoc(Object obj) throws IllegalAccessException, InstantiationException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(Autowired.class) != null) {
                field.setAccessible(true);
                Class<?> fieldClass = field.getType();
                // 接口不能被实例化，需要对接口进行特殊处理获取其子类，获取所有实现类
                if (fieldClass.isInterface()) {
                    // 当注入接口时，属性的名字与接口实现类名一致则直接从容器中获取
                    Object objByName = beanMap.get(getBeanMapKey(field.getName()));
                    if (objByName != null) {
                        field.set(obj, objByName);
                        // 递归依赖注入
                        doIoc(field.getType());
                    } else {
                        // 注入接口时，如果属性名称与接口实现类名不一致的情况下
                        List<Object> list = findSuperInterfaceByIoc(field.getType());
                        Assert.isTrue(
                                list != null && list.size() > 0,
                                "当前类" + obj.getClass() + "  不能注入接口 " + field.getType().getClass()
                                + "  ， 接口没有实现类不能被实例化"
                        );

                        field.set(obj, list.get(0));
                        System.out.println(obj.getClass().getSimpleName() + "依赖注入" + field.getName());
                        // 递归依赖注入
                        doIoc(field.getType());
                    }
                } else {
                    field.setAccessible(true);
                    Object bean = beanMap.get(getBeanMapKey(field.getName()));
                    field.set(obj, bean == null ? field.getType().newInstance() : bean);
                    System.out.println(obj.getClass().getSimpleName() + "依赖注入" + field.getName());
                }
                // 递归依赖注入
                doIoc(field.getType());
            }
        }
    }

    private List<Object> findSuperInterfaceByIoc(Class classz) {
        Set<String> beanNameList = beanMap.keySet();
        ArrayList<Object> list = new ArrayList<>();
        for (String beanName : beanNameList) {
            Object obj = beanMap.get(getBeanMapKey(beanName));
            Class<?>[] interfaces = obj.getClass().getInterfaces();
            if (ArrayUtils.contains(interfaces, classz)) {
                list.add(obj);
            }
        }
        return list;
    }

    /** 处理 @Component 类注解 */
    private void doComponent(Class<?> clazz) throws IllegalAccessException, InstantiationException {
        if (clazz.getAnnotation(Component.class) != null && !clazz.isInterface()) {
            beanMap.put(getBeanMapKey(clazz.getSimpleName()), clazz.newInstance());
        }
    }

    /** 扫描并存储目标类路径 */
    private void scanClassFile(String packagePath) {

        URL url = this.getClass().getClassLoader().getResource(packagePath);
        Assert.notNull(url, "扫描失败：扫描目录有误");

        File dir = new File(url.getFile());
        Assert.isTrue(dir.exists() && dir.isDirectory(), "扫描失败：未找到目录");

        File[] files = dir.listFiles();
        Assert.notNull(files, "扫描失败：文件异常");

        for (File file : files) {
            if (file.isDirectory()) {
                // 递归扫描
                scanClassFile(packagePath + "/" + file.getName());
            } else {
                // 是文件并且是以 .class结尾
                if (file.getName().endsWith(".class")) {
                    System.out.println(
                            String.format(
                                    "正在加载:%s.%s",
                                    packagePath.replace("/", "."),
                                    file.getName()
                            )
                    );
                    classPathSet.add(packagePath.replace("/", ".")
                                     + "." + file.getName().replace(".class", ""));
                }
            }
        }
    }

    public <T> T getBean(Class<T> clazz) {
        if (clazz.isInterface()) {
            List<Object> list = findSuperInterfaceByIoc(clazz);
            Object o = list.get(0);
            return (T) beanMap.get(getBeanMapKey(o.getClass().getSimpleName()));
        } else {
            return (T) beanMap.get(getBeanMapKey(clazz.getSimpleName()));
        }
    }
}
